#include "minicon.h"
#include "miniopt.h"

extern int err_ret(int errcode, const char *msg, const WCHAR *name);

enum _ELimits { eMAX_SETSIZE = 512 };

//===========================================================================================================

static bool in_class( WCHAR ch, DWORD mask, DWORD xmask )
{
   WORD w = 0;
   if ( !::GetStringTypeW(CT_CTYPE1, &ch, 1, &w) )
      return false;

   return ( (0 != (w & mask)) && (0 == (w & xmask)) );
}

static WCHAR lcmap( WCHAR ch, DWORD mode )
{
   ::LCMapStringW(LOCALE_USER_DEFAULT, mode, &ch, 1, &ch, 1);
   return ch;
}

//===========================================================================================================

struct TrSet
{
   union {
      struct _Single { WCHAR ch; } Single;
      struct _Range  { WCHAR c1; WCHAR c2; } Range;
      struct _Class  { DWORD mask; DWORD xmask; } Class;
      struct _Repeat { WCHAR ch; short count; } Repeat;
   } u;
   int pos;

   void init() { pos = -1; }

   bool next_single(WCHAR &next_ch) {
      if (pos >= 0) return false;
      next_ch = u.Single.ch; pos = 0; return true;
   } 
   bool next_range(WCHAR &next_ch) {
      int m = u.Range.c1 <= u.Range.c2 ? +1 : -1; 
      if (pos < 0) pos = (int)u.Range.c1;
      if (pos*m > (int)u.Range.c2*m) return false;
      next_ch = (WCHAR)pos; pos += m; return true;
   } 
   bool next_class(WCHAR &next_ch) {
      for(;;) {
         ++pos;
         if (pos > 0xffff ) return false;
         if ( u.Class.xmask < 0x10000 ) {
            if (!in_class((WCHAR)pos, u.Class.mask, u.Class.xmask)) continue;
            next_ch = (WCHAR)pos; return true;
         }
         else if ( u.Class.xmask == 0x10000 ) { // special case Upper->Lower translation
            if (!in_class((WCHAR)pos, C1_UPPER, 0)) continue;
            next_ch = lcmap((WCHAR)pos, LCMAP_LOWERCASE); 
            return true;
         }
         else { // special case Lower->Upper translation
            if (!in_class((WCHAR)pos, C1_LOWER, 0)) continue;
            next_ch = lcmap((WCHAR)pos, LCMAP_UPPERCASE); 
            return true;
         } 
      }
   } 
   bool next_repeat(WCHAR &next_ch) {
      if (pos < 0) pos = (int)u.Repeat.count;
      if (pos <= 0) return false;
      next_ch = u.Repeat.ch; --pos; return true;
   } 
   bool (TrSet::*next)(WCHAR &next_ch);

   void set_single( WCHAR ch ) { u.Single.ch=ch; next=&TrSet::next_single; }
   void set_range ( WCHAR c1, WCHAR c2 ) { u.Range.c1=c1; u.Range.c2=c2; next=&TrSet::next_range; }
   void set_repeat( WCHAR ch, short count ) { u.Repeat.ch=ch; u.Repeat.count=count; next=&TrSet::next_repeat; }
   void set_class ( DWORD mask, DWORD xmask, int setN ) {
      u.Class.mask = mask; u.Class.xmask = xmask; next = &TrSet::next_class;
      if ( setN == 2 ) {
         if      ( mask == C1_LOWER ) u.Class.xmask = 0x10000; // special case Upper->Lower translation
         else if ( mask == C1_UPPER ) u.Class.xmask = 0x20000; // special case Lower->Upper translation
      }
   }
};

//===========================================================================================================

static int unescape( WCHAR *str )
{
   unsigned val, len; int v;
   WCHAR *pi, *po;
   for ( pi = po = str; *pi; ++pi )
   {
      if ( L'\\' != *pi ) { *po++ = *pi; continue; }
      ++pi;
      v = -1;
      switch ( *pi ) {
         case L'\\': *po++ = L'\\'; break;
         case 'a':   *po++ = L'\a'; break;
         case 'b':   *po++ = L'\b'; break;
         case 'f':   *po++ = L'\f'; break;
         case 'n':   *po++ = L'\n'; break;
         case 'r':   *po++ = L'\r'; break;
         case 't':   *po++ = L'\t'; break;
         case 'v':   *po++ = L'\v'; break;
         case L'P':  *po++ = L'%';  break; //!!! MY !!!
         case L'Q':  *po++ = L'"';  break; //!!!!!!!!!!
         case L'O':  *po++ = L'\''; break; // extension
         case L'A':  *po++ = L'&';  break; // to
         case L'L':  *po++ = L'<';  break; // play
         case L'G':  *po++ = L'>';  break; // with
         case L'I':  *po++ = L'|';  break; // windows
         case L'S':  *po++ = L' ';  break; // shell
         case L'U':  *po++ = L'^';  break; //!!!!!!!!!!
         case L'E':  *po++ = L'=';  break; //!!!!!!!!!!
         case L'0': case L'1': case L'2': case L'3': case L'4': case L'5': case L'6': case L'7':
            len = 3; v = (int)Opt::wtou(pi, 8, &len); pi += len - 1;
         break;
         case 'x':
            len = 2; v = (int)Opt::wtou(pi+1, 16, &len); pi += 1 + len - 1;
         break;
         case 'u':
            len = 4; val = Opt::wtou(pi+1, 16, &len); pi += 1 + len - 1;
            *po++ = (WCHAR)val;
         break;
      }
      if ( v >= 0 ) {
         *po = (WCHAR)v;
         ::MultiByteToWideChar(CP_ACP, 0, (LPCSTR)&v, 1, po, 1);
         ++po;
      }
   }
   *po = L'\0';
   return (int)(unsigned)(po - str);
}

//===========================================================================================================

#define C1_any C1_ALPHA | C1_CNTRL | C1_DIGIT | C1_PUNCT | C1_SPACE 

static struct reg_class { const WCHAR *cl; DWORD mask; DWORD xmask; } classes[] =
{ { L"alnum:]", C1_ALPHA | C1_DIGIT     , 0 }
, { L"alpha:}", C1_ALPHA                , 0 }
, { L"blank:]", C1_BLANK                , 0 }
, { L"cntrl:}", C1_CNTRL                , 0 }
, { L"digit:]", C1_DIGIT                , 0 }
, { L"graph:]", C1_any, C1_CNTRL | C1_SPACE }
, { L"lower:]", C1_LOWER                , 0 }
, { L"print:]", C1_any           , C1_CNTRL }
, { L"punct:]", C1_PUNCT                , 0 }
, { L"space:]", C1_SPACE                , 0 }
, { L"upper:]", C1_UPPER                , 0 }
, { L"xdigit:]",C1_XDIGIT               , 0 }
};

//===========================================================================================================

static int set_parse( WCHAR *s, int setN, TrSet* &pset, int &ne )
{
   pset = NULL; Win::alloc((void* &)pset, sizeof(TrSet) * (eMAX_SETSIZE+1));

   ne = 0;
   int i, j, ns = unescape(s);
   for (i=0; i < ns; ++i)
   {
      if ( ne >= eMAX_SETSIZE ) break;

      if ( L'[' == s[i] ) {
         if ( L':' == s[i+1] && i < ns - 6 ) {
            for (j=0; j < idim(classes); ++j) {
               int nc = ::lstrlenW(classes[j].cl);
               if ( i + 2 + nc > ns ) continue;
               if (CSTR_EQUAL == ::CompareStringW(LOCALE_USER_DEFAULT,SORT_STRINGSORT,classes[j].cl,nc,s+i+2,nc))
               {
                  pset[ne++].set_class(classes[j].mask, classes[j].xmask, setN);
                  i += 2 + nc - 1;
                  break;                                       // [:class:]
               }
            }
            if ( j < idim(classes) ) continue;
         }
         if (i < ns-4 && L'-' == s[i+2] && L']' == s[i+4]) {
            pset[ne++].set_range(s[i+1], s[i+3]);      // [c-c]
            i += 5 - 1;
            continue;
         }
         if ( setN > 1 && i < ns-3 && L'*' == s[i+2] ) {
            if ( L']' == s[i+3] ) {
               pset[ne++].set_repeat(s[i+1], (short)-1);   // [c*]
               i += 4 -1;
               continue;
            }
            if ( s[i+3] >= L'0' && s[i+3] <= L'9' ) {
               unsigned len=0, v;
               v = Opt::wtou(s+i+3, 0, &len);
               if ( L']' == s[i+3+len] ) {
                  pset[ne++].set_repeat(s[i+1], (short)v); // [c*n]
                  i += 4+len - 1;
                  continue;
               }
            }
         }
      }
      if (i < ns-2 && L'-' == s[i+1]) {
         pset[ne++].set_range(s[i], s[i+2]);              // c-c
         i += 3 - 1;
      }
      else {
         pset[ne++].set_single(s[i]);                      // c
      }
   }
   return 0;
}

//===========================================================================================================

static void set_adjust_rep( TrSet *set, int ne, int add, int *ptbl )
{
   while ( ne-- > 0 ) {
      if ( set->next == &TrSet::next_repeat && set->u.Repeat.count < 0 ) {
         set->u.Repeat.count = (short)add;
         if ( add > 0 ) ptbl[set->u.Repeat.ch] = 1;
         add = 0;
      }
      ++set;
   }
}

//===========================================================================================================

static int set_table( TrSet* set, int ne, bool complement, int* &ptbl, int &len )
{
   int tlen = 0x10000; ptbl = NULL; Win::alloc((void* &)ptbl, sizeof(int) * tlen);
   Win::bset(ptbl, -1, sizeof(int)*tlen);

   int nch = 0; WCHAR ch = L' ';
   while ( ne-- > 0 ) {
      set->init();
      while ( (set->*set->next)(ch) ) { ++nch; ptbl[ch] = +1; }
      ++set;
   }
   if ( complement ) {
      nch = 0;
      while ( --tlen >= 0 ) if ( (ptbl[tlen] = -ptbl[tlen]) > 0 ) ++nch;
   }
   len = nch;
   return 0;
}

//===========================================================================================================

static void set_trans( TrSet *set1,int n1, TrSet *set2,int n2, int *tbl1 )
{
   WCHAR ch1, ch2 = L' ';
   set2->init();
   while ( n1-- > 0 )
   {
      set1->init();
      while ( (set1->*set1->next)(ch1) ) {
         while ( n2 > 0 && !(set2->*set2->next)(ch2) ) { --n2; ++set2; set2->init(); }
         tbl1[ch1] = ch2;
      }
      ++set1;
   }
}

static void set_trans_complement( TrSet *set2,int n2, int *tbl1 )
{
   WCHAR ch2 = L' ';
   set2->init();
   int i, tlen = 0x10000;
   for ( i = 0; i < tlen; ++i )
   {
      if ( tbl1[i] < 0 ) continue;
      while ( n2 > 0 && !(set2->*set2->next)(ch2) ) { --n2; ++set2; set2->init(); }
      tbl1[i] = ch2;
   }
}

//===========================================================================================================
//===========================================================================================================

int tr (
   WCHAR **args, int na, const WCHAR *fnm_i, const WCHAR *fnm_o,
   bool complement, bool del, bool squeeze, bool truncate )
{
   TrSet *set1 = NULL, *set2 = NULL;
   WCHAR sw[1024*4/2 - 128];

   int rc = 0, nr, ir, nw, last_char=-1, n1=0,n2=0, l1,l2, *tbl1=NULL, *tbl2=NULL;
   
   if ( na < 1 || (na < 2 && del && squeeze)) return Opt::opt_err(Opt::Err_Missed_Param);
   if ( na > 2 ) return Opt::opt_err(Opt::Err_TooMany_Params);
   bool translate = (na == 2) && !del;

   if (fnm_i && *fnm_i && 0 != ::lstrcmpW(L"-", fnm_i)) {
      if (!Con::setInp(fnm_i)) {
         rc = err_ret(-23, "Can't open input", fnm_i); goto e;
      }
   }
   if (fnm_o && *fnm_o && 0 != ::lstrcmpW(L"-", fnm_o)) {
      if (!Con::setOut(fnm_i)) {
         rc = err_ret(-24, "Can't create output", fnm_o); goto e;
      }
   }
   if ( rc < 0 ) goto e;
   ::SetLastError(0);

   if ( truncate ) return err_ret(-20, "-t option is not supported", NULL);
   if ( !translate && !del && !squeeze ) return err_ret(-22, "translation requires 2 Sets", NULL);

   rc = set_parse(args[0], 1, set1, n1); if ( 0 != rc ) goto e;
   rc = set_table(set1, n1, complement, tbl1, l1); if ( 0 != rc ) goto e;
   
   if (translate || (del && squeeze)) {
      rc = set_parse(args[1], 2, set2, n2); if ( 0 != rc ) goto e;
      rc = set_table(set2, n2, false, tbl2, l2); if ( 0 != rc ) goto e;
      if ( l2 < l1 ) set_adjust_rep(set2, n2, l1-l2, tbl2);
   }
   if ( translate )
      if (complement) set_trans_complement(set2,n2,tbl1); else set_trans(set1,n1, set2,n2, tbl1);

   if ( squeeze && !translate && !del ) tbl2 = tbl1; // tbl2 is always squeeze table

   while ((nr = rc = Con::read(sw, idim(sw))) > 0)
   {
      for ( ir = nw = 0; ir < nr; ++ir )
      {
         WCHAR ch = sw[ir];
         if ( translate ) {
            int ich = tbl1[ch];
            if ( ich >= 0 ) ch = (WCHAR)ich;
         }
         else if ( del ) {
            if ( tbl1[ch] >= 0 ) continue; // deleted CHAR
         }

         if ( squeeze && (int)ch == last_char && tbl2[ch] >= 0 )
            continue;                      // squeezed

         last_char = (int)ch;
         sw[nw++] = ch;
      }
      if ((rc = Con::write(sw, nw)) < 0)
         break;
   }
   if (rc >= 0)
      rc = 0;
   else {
      const char *pe = "read error:"; const WCHAR *fnm = fnm_i;
      if ( rc == -25 ) { pe = "write error:"; fnm = fnm_o; }
      if ( !fnm || !fnm[0] ) fnm = L"-";
      err_ret(rc, pe, fnm);
   }
e: return rc;
}

//===========================================================================================================
//===========================================================================================================
